如何用Rust和Napi

您所在的位置:网站首页 js csv文件check 如何用Rust和Napi

如何用Rust和Napi

2023-06-02 13:55| 来源: 网络整理| 查看: 265

原文:How to process a CSV file five times faster in NodeJs with Rust and Napi rs

因为原文的文件目录和文件名比较随便,所以这里会修改一下。而且修改成适合win系统环境的代码,在ubuntu下也试过没任何问题。

引言

本教程将教我们如何使用Rust和Napi rs在NodeJS中处理CSV文件。我们将使用Rust编程语言来加速CSV文件的处理,并使用Napi rs库创建本地nodejs扩展。

CSV 文件

在本教程中,我使用了以下CSV文件。

解压缩后,您将得到几个文件,最大的一个约为900Mb(这里只使用最大的这个文件,改名data.csv)

使用Nodejs处理

我没有使用特殊的库来处理文件,以避免将性能问题归咎于库。

所以对于nodejs,我使用了readline模块,这是nodejs核心的一部分。

这里先把data.csv复制到js文件同一个目录下,或者下面的路径改成解压出来的文件的目录也行

const readline = require("node:readline") const fs = require('node:fs') // we will sum the last column of the CSV file let sum = 0 let isHeader = true const start = Date.now() const file = fs.createReadStream('./data.csv') // 把压缩包解压出来最大的文件改名成data.csv,方便代码编写 const lineReader = readline.createInterface({ input: file, }) lineReader .on("line", (line) => { // we need to skip the first line which is the header if (isHeader) { isHeader = false return } // ource csv has a comma as delimiter const fields = line.trimEnd().split(",") // we get the last column and parse the value to integer const last = parseInt(fields[fields.length - 1]) // there are acouple of lines with broken values we should ignore those if (!isNaN(last)) { sum += last } }) .on("close", () => { console.log(`总共${sum}行`) console.log(`用时${(Date.now() - start) / 1000}s`,) })

运行一下看看时间:

> node test1.js 总共3345553228行 用时 11.334s

脚本在11秒处理了文件。平均吞吐量是75MiB/s左右。

使用Rust处理

我使用以下代码进行Rust处理,它具有与Nodejs版本相同的逻辑。

use std::io::{BufRead, BufReader}; use std::fs::File; use std::time::{SystemTime}; fn main() { let start = SystemTime::now(); let mut sum = 0; let file = File::open("./data.csv").expect("Unable to open file"); let reader = BufReader::new(file); let mut is_header = true; for line in reader.lines() { let line = line.expect("Unable to read line"); if is_header { is_header = false; continue; } // we get the last column and parse the value to integer let res = line .trim_end() .split(",") .last() .unwrap() .parse::() // some values are as floats but we still parse everything to int .unwrap_or(0.0) as i64; sum += res; } println!("总共{}行", sum); println!("用时{}s", start.elapsed().unwrap().as_millis() as f64 / 1000.0) }

运行如下命令后:

cargo run --release

关键点是用 --release 标志运行;否则,性能会差很多。

Compiling nodejs_vs_rust_stream v0.1.0 (/home/alxolr/Work/rust_land/nodejs_vs_rust_stream) Finished release [optimized] target(s) in 0.17s Running `target/release/nodejs_vs_rust_stream` 总共3345553228行 用时2.542s

我们可以注意到Rust版本的速度比Nodejs版本快五倍。吞吐量为326MiB/s。

现在一个合乎逻辑的问题出现了:如果我们已经有了一个巨大的Nodejs代码库,该怎么办?我们不能就这么搬去Rust

有一种方法可以在Nodejs中使用Rust,那就是使用Napi rs。

Napi rs

Napi-rs是一个允许你在Rust中创建Nodejs模块的库。它是Napi C库的包装器。

NAPI代码被编译到Nodejs可以加载的动态库中。因此,您可以使用NAPI在C/C++或Rust中创建NodeJS模块。

为了生成新的Napi模块,您需要安装Napi rs cli工具:

npm i -g @napi-rs/cli

@napi-rs/cliz用的是yarn,如果没装yarn记得把yarn也安装一下

然后,您可以使用以下命令创建一个新模块:

napi new async_csv_reader

然后提供的选项全部选默认即可

生成项目Rust代码后,将下面代码写在src/lib.rs文件中。

#![deny(clippy::all)] use std::{ fs::File, io::{self, BufRead}, path::Path, }; use napi::{bindgen_prelude::AsyncTask, JsNumber, Task}; #[macro_use] extern crate napi_derive; // we want our function to be a promise to be executed asynchronously to not block the event loop in nodejs // and for that we need to create this weird structs AsyncReadCsv and impl the Task trait for it. #[napi] pub fn read_csv_async(path: String) -> AsyncTask { AsyncTask::new(AsyncReadCsv { path }) } pub struct AsyncReadCsv { path: String, } impl Task for AsyncReadCsv { type Output = i64; type JsValue = JsNumber; fn compute(&mut self) -> napi::Result { Ok(read_csv(self.path.clone())) } fn resolve(&mut self, env: napi::Env, output: Self::Output) -> napi::Result { env.create_int64(output) } } // this is the main function that receive the path to the csv file // and start processing the data line by line fn read_csv(path: String) -> i64 { let lines = read_lines(Path::new(&path)).unwrap(); let mut sum = 0; for line in lines { if let Ok(ip) = line { let res = ip .trim_end() .split(",") .last() .unwrap() .parse::() .unwrap_or(0.0) as i64; sum += res; } } sum } // useful function to read the lines from a file fn read_lines

(filename: P) -> io::Result where P: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) }

在你的项目中运行npm run build后,你会得到一个index.js、index.d.ts和async_csv_reader.win32-x64-msvc.node文件,你可以从nodejs中调用它们。

我这里使用的是rust-gnu工具链进行编译,所以得到的node文件是win32-x64-gnu.node,需要去到index.js将里面的win32-x64-msvc全部改成win32-x64-gnu

在根目录新建个test.js,并且把csv文件复制过来(不复制,直接改下面路径也行):

const { readCsvAsync } = require('./index') async function test() { const start = Date.now() const sum = await readCsvAsync('./data.csv') console.log("总共", sum) console.log("用时", (Date.now() - start) / 1000 + 's') } test()

然后运行:

> node test 总共 3345553228 用时 2.32s 总结

对于你的nodejs代码的CPU密集部分,你需要处理大量的数据,最好使用Rust,你可以创建一个本地扩展,并从nodejs调用它。

这种Rust和nodejs的结合非常引人注目,并且充分利用了两者的优点。

我希望这篇文章对你有所帮助。如果你喜欢它,请与你的朋友分享并留下评论;我很乐意回答所有的问题。



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3